﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal static class NavigationFilterUtils
  {
    internal static bool IsSupported(
      DataSourceProvider provider,
      IEnumerable<SortDescription> sortOrder )
    {
      if( sortOrder == null )
        throw new ArgumentNullException( "sortOrder" );

      return NavigationFilterUtils.IsSupported(
               provider,
               sortOrder.Select( ( sd ) => sd.PropertyName ) );
    }

    internal static bool IsSupported(
      DataSourceProvider provider,
      IEnumerable<string> propertiesNames )
    {
      if( provider == null )
        throw new ArgumentNullException( "provider" );

      if( propertiesNames == null )
        throw new ArgumentNullException( "propertiesNames" );

      // The provider doesn't support filtering.
      if( !provider.CanFilter )
        return false;

      // At least one property name must be specified.
      if( !propertiesNames.Any() )
        return false;

      // Get the operators that must be supported by the DataSourceProvider.
      IEnumerable<FilterOperator> navigationOperators = new FilterOperator[] { 
                                                          FilterOperator.Equal,
                                                          FilterOperator.GreaterThan,
                                                          FilterOperator.GreaterThanOrEqual,
                                                          FilterOperator.LessThan,
                                                          FilterOperator.LessThanOrEqual };

      // Get the type of every member that is part of the sort.
      IEnumerable<Type> membersType = ( from propertyName in propertiesNames
                                        select provider.GetElementMemberType( propertyName ) ).Distinct();

      // The SortPosition is considered supported if all the necessary operators are supported by
      // the DataSourceProvider.
      return ( from memberType in membersType
               let supportedOperators = provider.SupportedFilterOperators( memberType )
               let matchingOperators = navigationOperators.Intersect( supportedOperators )
               select matchingOperators.SequenceEqual( navigationOperators ) ).All( ( result ) => result );
    }

    internal static FilterExpressionBase CreateFilterExpression(
      IList<SortDescription> sorting,
      IList<object> sortValues )
    {
      if( sorting == null )
        throw new ArgumentNullException( "sorting" );

      if( sortValues == null )
        throw new ArgumentNullException( "sortValues" );

      if( sorting.Count != sortValues.Count )
        throw new ArgumentException( "The specified sort-description count must match the sort-value count.", "sorting" );

      FilterExpressionBase filter = null;
      for( int i = 0; i < sorting.Count; i++ )
      {
        FilterExpressionBase currentFilter = NavigationFilterUtils.CreateFilterExpression( 
                                               sorting[ i ].PropertyName, 
                                               SortDirection.None, 
                                               sortValues[ i ], 
                                               true );

        if( filter != null )
        {
          filter = new AndFilterExpression( filter, currentFilter );
        }
        else
        {
          filter = currentFilter;
        }
      }

      return filter;
    }

    internal static FilterExpressionBase CreateFilterExpression(
      IList<SortDescription> sorting,
      IList<object> sortValues,
      bool reverseDirection,
      bool includeValue )
    {
      if( sorting == null )
        throw new ArgumentNullException( "sorting" );

      if( sortValues == null )
        throw new ArgumentNullException( "sortValues" );

      IEnumerable<Tuple<SortDescription, object>> sort = sorting.Take( sortValues.Count() ).Zip(
          sortValues,
          ( x, y ) => new Tuple<SortDescription, object>( x, y ) );

      FilterExpressionBase filter = null;
      int propertyCount = sorting.Count;

      // This section creates filters that will enable us
      // to jump over a group of items.  The actual
      // sort order must be taken into account to build
      // the right expression.
      //
      // If the sorted properties are  : A, B, C
      // and the sorting values are : x, y, z
      // and we are fetching forward, then the desired filter
      // expression will look like:
      //
      //      (A > x)
      // OR ( (A = x) AND (B > y) )
      // OR ( (A = x) AND (B = y) AND (C > z) )
      //
      // The ">" operator should be replace by "<" if
      // we are fetch backward.  The last comparison
      // value (ie. C > z) must be ">=" if the starting
      // item must be included in the results.
      if( sortValues.Count >= 1 )
      {
        bool hasOneSortParameter = ( sortValues.Count == 1 );
        OrFilterExpression orExpression = new OrFilterExpression();
        FilterExpressionBase currentExpression = null;

        for( int i = 0; i < sortValues.Count; i++ )
        {
          if( i == 0 )
          {
            SortDescription sortDescription = sorting[ i ];
            SortDirection direction = ( sortDescription.Direction == ListSortDirection.Ascending ) ^ reverseDirection
                                      ? SortDirection.Ascending : SortDirection.Descending;

            currentExpression = NavigationFilterUtils.CreateFilterExpression(
                                  sortDescription.PropertyName,
                                  direction,
                                  sortValues[ i ],
                                  ( hasOneSortParameter && includeValue ) );
          }
          else
          {
            AndFilterExpression andExpression = new AndFilterExpression();
            bool isLastExpression = ( i == sortValues.Count - 1 );

            for( int j = 0; j <= i; j++ )
            {
              bool isLastOfCurrentExpression = ( j == i );
              bool isLastOfAllExpression = ( isLastExpression && isLastOfCurrentExpression );
              SortDescription sortDescription = sorting[ j ];
              SortDirection direction = SortDirection.None;

              if( isLastOfCurrentExpression )
              {
                direction = ( sortDescription.Direction == ListSortDirection.Ascending ) ^ reverseDirection
                              ? SortDirection.Ascending
                              : SortDirection.Descending;
              }

              FilterExpression expression = NavigationFilterUtils.CreateFilterExpression(
                                              sortDescription.PropertyName,
                                              direction,
                                              sortValues[ j ],
                                              ( isLastOfAllExpression && includeValue ) );

              andExpression.Add( expression );
            }

            currentExpression = andExpression;
          }

          orExpression.Add( currentExpression );
        }

        filter = ( hasOneSortParameter )
                   ? currentExpression
                   : orExpression;
      }

      return filter;
    }

    private static FilterExpression CreateFilterExpression(
      string propertyName,
      SortDirection sortDirection,
      object constantValue,
      bool includeValue )
    {
      FilterOperator filterOperator = FilterOperator.Equal;

      if( sortDirection != SortDirection.None )
      {
        if( sortDirection == SortDirection.Ascending )
        {
          filterOperator = ( includeValue )
                             ? FilterOperator.GreaterThanOrEqual
                             : FilterOperator.GreaterThan;
        }
        else
        {
          filterOperator = ( includeValue )
                             ? FilterOperator.LessThanOrEqual
                             : FilterOperator.LessThan;
        }
      }

      // The navigation filter must be case-sensitive if the remove data source
      // is case-sensitive in order to get the expected result.
      return new FilterExpression( propertyName, filterOperator, constantValue );
    }
  }
}
